﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;

namespace MicroDal
{
    internal abstract class MapGenerator
    {
        internal static Expression<Func<DbDataReader, string, Type, object>> READ_FIELD =
            (reader, columnName, targetType) => (reader.IsDBNull(reader.GetOrdinal(columnName)) ?
                (targetType.IsValueType ? Activator.CreateInstance(targetType) : null) :
                reader.GetValue(reader.GetOrdinal(columnName)));

        private static ParameterExpression READER_PARAM = Expression.Parameter(typeof(DbDataReader), "reader");

        public static Expression<Func<DbDataReader, T>> DbDataReaderMapper<T>()
        {
            var t = typeof(T);

            if (!t.IsValueType && t.GetConstructor(Type.EmptyTypes) != null)
            {
                // Parameterless instance creation
                var bindings = new List<MemberBinding>();

                foreach (var prop in typeof(T).GetProperties())
                {
                    // Only properties with public setters should be used
                    if (prop != null && prop.GetSetMethod() != null)
                    {
                        InvocationExpression ie =
                            Expression.Invoke(READ_FIELD, READER_PARAM, Expression.Constant(prop.Name), Expression.Constant(prop.PropertyType));
                        if (prop.PropertyType.IsValueType)
                            bindings.Add(Expression.Bind(prop, Expression.Convert(ie, prop.PropertyType)));
                        else
                            bindings.Add(Expression.Bind(prop, Expression.ConvertChecked(ie, prop.PropertyType)));
                    }
                }
                MemberInitExpression initT = Expression.MemberInit(Expression.New(typeof(T)), bindings);

                return Expression.Lambda<Func<DbDataReader, T>>(initT, READER_PARAM);
            }
            else
            {
                // Gets constructor with most parameters, and uses it for instance creation
                var constructor = t.GetConstructors().OrderByDescending(x=>x.GetParameters().Length).First();
                var parameters = new List<Expression>();

                foreach (var par in constructor.GetParameters())
                {
                    InvocationExpression ie =
                        Expression.Invoke(READ_FIELD, READER_PARAM, Expression.Constant(par.Name), Expression.Constant(par.ParameterType));

                    if (par.ParameterType.IsValueType)
                        parameters.Add(Expression.Convert(ie, par.ParameterType));
                    else
                        parameters.Add(Expression.ConvertChecked(ie, par.ParameterType));
                }

                return Expression.Lambda<Func<DbDataReader, T>>(Expression.New(constructor, parameters), READER_PARAM);
            }

        }
    }
}
